package org.reborn.utils

import org.reborn.math.util._

class SquareHeightMap(val length:Int){
  require(length > 0)
  val numElements = length * length
  
  private val array = new Array[Float](numElements)
  
  def this(hm:SquareHeightMap) = {
    this(hm.length)
    for(i <- 0 until numElements) { 
      array(i) = hm.array(i)
    }
  }
  
  def apply(x:Int,y:Int):Float = { array(x * length + y ) }
  def update(x:Int,y:Int,value:Float):Unit = { array(x * length + y) = value }
  
  def apply(x:Int,y:Int,repeat:Boolean):Float = {
    if(!repeat) apply(x,y)
    else {
      apply(wrap(x),wrap(y))
    }
  }
  
  def update(x:Int,y:Int,repeat:Boolean,value:Float):Unit = { 
    if(!repeat) { update(x,y,value) }
    else { update(wrap(x),wrap(y),value) }
  }
  
  private def wrap(index:Int):Int = {
    val value = index % length
    if(value < 0 )
      length + value
    else
      value
  }
  
  def get1DArray() = array
  
  /**
   * Transform each height value using the specified transform function
   */
  def transformBy(transformFunc:(Float)=>Float) = {
    for(i <- 0 until numElements) { 
      array(i) = transformFunc(array(i))
    }
    this
  }
  
  /**
   * Scaling every value in the map so that the largest value is max, 
   * and the smallest is min.  All values in between are linearly interpolated.
   */
  def scale(min:Float,max:Float) = {
    var maxCur = Float.MinValue
    var minCur = Float.MaxValue
    
    for(i <- 0 until numElements) { 
      val curVal = array(i)
      if(curVal > maxCur)
        maxCur = curVal
      if(curVal < minCur)
        minCur = curVal
    }
    val scaleUnit = maxCur - minCur
    val scaleUp = max - min
    val scale = scaleUp / scaleUnit
    
    transformBy{(h) => ( (h - minCur) * scale) + min  }
  }
  
}

object HeightMapUtil {

  /**
   * Create a SquareHeightMap of specified length, where all height values are zero
   */
  def ZeroHeightMap(length:Int) = { new SquareHeightMap(length) }
  
  /**
   * Create a SquareHeightMap of specified length, where all of the height values
   * are equal to the specified height
   */
  def FlatHeightMap(length:Int,height:Float) = {
    HeightMapFromFunction(length){(_,_) => height }
  }
  
  /**
   * Create a SquareHeightMap of specified length, where the height values are
   * generated by the specified function
   */
  def HeightMapFromFunction(length:Int)(func:(Int,Int)=>Float):SquareHeightMap = {
    val map = new SquareHeightMap(length)
    for(xInd <- 0 to (length-1); yInd <- 0 to (length-1)){
      map(xInd,yInd) = func(xInd,yInd)
    }
    map
  }

  /**
   * Create a new SquareHeightMap by transforming each height value of an existing
   * one by the specified transform function
   */
  def Transform(hm:SquareHeightMap)(transformFunc:(Float)=>Float):SquareHeightMap = {
    new SquareHeightMap(hm).transformBy(transformFunc)
  }
  
  /**
   * Create a new SquareHeightMap by scaling the given one so that the largest
   * value in the new map is max, and the smallest is min.  All values in between
   * are linearly interpolated
   */
  def Scale(hm:SquareHeightMap,min:Float,max:Float):SquareHeightMap = {
    new SquareHeightMap(hm).scale(min,max)
  }
  
  /**
   * Create a new SquareHeightMap by combining twn SqareHeightMaps using 
   * the specified combine function
   */
  def Combine(hm1:SquareHeightMap,hm2:SquareHeightMap)(combineFunc:(Float,Float)=>Float) = {
    val length = hm1.length
    require(length == hm2.length)
    
    val nMap = new SquareHeightMap(length)
    for(x <- 0 to (length-1); y <- 0 to (length-1)) { 
      nMap(x,y) = combineFunc(hm1(x,y), hm2(x,y))
    }
    nMap
  }
  
  /**
   * Create a new SquareHeightMap of length 2^powerOf2.  The height values
   * are calculated using the diamond-square fractal algorithm
   * @param powerOf2 Determines the length of 1 side of the terrain where 
   *  length = 2 to the powerOf2 power
   * @param cornerSeed A starting value to give to the corner of the terrain, 
   *  that all other heights will be recursively calculated from
   * @param rangeReduction A value that will be recursively multiplied to generate 
   *  increasingly-detailed height values.  For example, if rangeReduction is .5, 
   *  the first pass will be made up of random values in the range (-.5, .5); second 
   *  pass (-.5*.5,.5*.5) or (-.25,.25); then (-.25*.5,.25*.5) or (-.125,.125); etc...
   */
  def DiamondSquare(powerOf2:Int,cornerSeed:Float,rangeReduction:Float):SquareHeightMap = {
    DiamondSquare(powerOf2,cornerSeed,0.0f,rangeReduction)
  }
  
  def DiamondSquare(powerOf2:Int,cornerSeed:Float,centerSeed:Float,rangeReduction:Float):SquareHeightMap = {
    require(rangeReduction >= 0.0)
    require(powerOf2 > 0 && powerOf2 < 30)
    
    val length = 1 << powerOf2
    val hm = ZeroHeightMap(length)
    
    //Seed the corners of the map with the given value.  Since this HeightMap
    //is repeatable across the x and y limits, the 4 corners map to a single value
    //at (0,0)
    hm(0,0) = cornerSeed
    hm(length / 2, length / 2, true) = centerSeed
    
    //Calculates the average of the four corners of a square and adds a random value to it
    def diamondStep(dLength:Int,range:Float):Unit = {
      print("D: " + dLength + " - ")
      val halfDist = dLength / 2
      for(x <- List.range(halfDist,length - halfDist + 1,dLength);
          y <- List.range(halfDist,length - halfDist + 1,dLength);
      	  if( hm(x,y,true) == 0.0f) ) {
            val avg = (hm(x - halfDist,y - halfDist,true) + 
                       hm(x + halfDist,y - halfDist,true) + 
                       hm(x + halfDist,y + halfDist,true) + 
                       hm(x - halfDist,y + halfDist,true)) / 4.0f
            hm(x,y,true) = avg + (range * MathUtils.random())
            //print("("+x+","+y+")="+hm(x,y,true)+" ")
          }
      println()
    }
    
    //Calculates the average of the four points of a diamond and adds a random value to it
    def squareStep(sLength:Int,range:Float):Unit = {
      print("S: " + sLength + " - ")
      val halfDist = sLength / 2
      for(x <- List.range(0,length, halfDist);
          y <- List.range(0,length, halfDist);
      	  if( hm(x,y,true) == 0.0f);
          if (x%sLength != 0 && y%sLength == 0 || x%sLength == 0 && y%sLength != 0)){
            val avg = (hm(x - halfDist,y,true) + 
                       hm(x + halfDist,y,true) + 
                       hm(x,y - halfDist,true) + 
                       hm(x,y + halfDist,true)) / 4.0f
            hm(x,y,true) = avg + (range * MathUtils.random())
            //print("("+x+","+y+")="+hm(x,y,true)+" ")
          }
      println()
    }
    
    //Recursive function that basically just calls the diamond step
    //and sqaure step for each power of 2 level (and range)
    def doStep(length:Int,range:Float):Unit = {
      diamondStep(length,range)
      squareStep(length,range)
      
      if(length > 2)
        doStep(length/2,range * rangeReduction)
    }
    
    doStep(length,rangeReduction)
    
    hm
  }
}
